/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/

#include "pch.h"

#include <db/engines/jet/db_jet_error.h>
#include <db/engines/jet/db_jet_engine.h>
#include <db/db_field_definition_t.h>
#include <db/db_field_instance_t.h>
#include <db/db_index_field_definition.h>
#include <db/db_select_criteria.h>
#include <db/db_index_definition.h>
#include <db/db_table_definition.h>

#pragma comment(lib, "esent.lib")

using namespace test_wrappers::storage;
// helper types
struct strings_buffer
{
    std::vector<std::wstring*> strings;
    ~strings_buffer()
    {
        std::vector<std::wstring*>::iterator it;
        for (it = strings.begin() ; it < strings.end() ; ++it)
        {
            delete *it;
        }
    }
};

struct string_buffer
{
    unsigned int byteCount;
    unsigned int charCount;
    wchar_t* buffer;

    string_buffer(unsigned int byteCount_) :
        byteCount(byteCount_),
        buffer(nullptr)
    {
        charCount = byteCount / sizeof(wchar_t);
        buffer = new wchar_t[charCount];
        buffer[charCount - 1] = L'\0';
    }
    void rightTrimSpaces()
    {
        if (charCount < 2)
        {
            return;
        }
        buffer[charCount - 1] = L'\0';
        unsigned int lastChar = charCount - 2;
        while (lastChar >= 0 && buffer[lastChar] == L' ')
        {
            buffer[lastChar] = L'\0';
            if (lastChar == 0)
            {
                break;
            }
            --lastChar;
        }
    }
    ~string_buffer()
    {
        delete [] buffer;
    }
};


db_jet_engine::db_jet_engine() :
        _instance(JET_instanceNil),
        _sessionId(JET_sesidNil),
        _dbID(JET_dbidNil)
{
}

std::shared_ptr<db_jet_engine> db_jet_engine::create()
{
    return std::make_shared<db_jet_engine>();
}

db_jet_engine::~db_jet_engine()
{
    shutdown();
}

void db_jet_engine::shutdown()
{
    if (_dbID != JET_dbidNil)
    {
        JetCloseDatabase(_sessionId, _dbID, 0 /* reserved */);
        _dbID = JET_dbidNil;
    }
    if (_sessionId != JET_sesidNil)
    {
        JetEndSession(_sessionId, 0 /* reserved */);
        _sessionId = JET_sesidNil;
    }
    if (_instance != JET_instanceNil)
    {
        JetTerm2(_instance, JET_bitTermComplete);
        _instance = JET_instanceNil;
    }
}

void db_jet_engine::setPageSize(
    const unsigned int& size
    )
{
    jet_call(
        ::JetSetSystemParameter(
            &_instance, 
            _sessionId, 
            JET_paramDatabasePageSize, 
            size, 
            nullptr
            ));
}

void db_jet_engine::setTempPath(
    const std::wstring& tempPath
    )
{
    jet_call(
        ::JetSetSystemParameter(
            &_instance, 
            0, 
            JET_paramTempPath, 
            0, 
            tempPath.c_str()
            ));
}

void db_jet_engine::setLogFilePath(
    const std::wstring& logFilePath
    )
{
    jet_call(
        ::JetSetSystemParameter(
            &_instance, 
            0, 
            JET_paramLogFilePath, 
            0, 
            logFilePath.c_str()
            ));
}

void db_jet_engine::setSystemPath(
    const std::wstring& systemPath
    )
{
    jet_call(
        ::JetSetSystemParameter(
            &_instance, 
            0, 
            JET_paramSystemPath, 
            0, 
            systemPath.c_str()
            )); 
}

void db_jet_engine::createInstance(
    const std::wstring& workingFolderName
    )
{
    jet_call(
        ::JetCreateInstance2(
            &_instance,
            L"{0A9A6617-8AE9-4c5e-AF28-01D5D4820C23}",
            nullptr,
            0));

    setTempPath(workingFolderName);
    setLogFilePath(workingFolderName);
    setSystemPath(workingFolderName);

    jet_call(
        ::JetInit3(
            &_instance,
            nullptr,
            0
            ));
}

void db_jet_engine::beginSession()
{
    jet_call(
        ::JetBeginSession(
            _instance,    // zero as ESE instance
            &_sessionId,  // return value
            0,            // reserved
            0             // reserved
            ));
}

void db_jet_engine::createDatabase(
    const std::wstring& fileName
    )
{
    jet_call(
        ::JetCreateDatabase2(
            _sessionId,   // the session identifier
            fileName.c_str(),    // file name
            0,            // reserved
            &_dbID,       // return value
            0             // zero flag - just create the database
            ));
}

void db_jet_engine::openDatabase(
    const std::wstring& fileName
    )
{
    JET_ERR err = JET_errSuccess;
    err = ::JetAttachDatabase2(
            _sessionId,
            fileName.c_str(),
            0,
            0);

    if (err != JET_errSuccess)
    {
        if (err == JET_errFileNotFound)
        {
            createDatabase(fileName);
        }
        else
        {
            throw jet_exception("Failed to attach database!", err);
        }
    }
    else
    {
        jet_call(
            ::JetOpenDatabaseW(
                _sessionId,
                fileName.c_str(),
                nullptr,
                &_dbID,
                0 // not exclusive, and not read-only...
                ));
    }
}

table_instance db_jet_engine::instantiateTable(
    const table_definition& tableDefinition
    )
{
    std::lock_guard<std::recursive_mutex> lock(_mutex);

    JET_TABLEID tableId;
    table_instance tableInstance;

    JET_ERR err = JET_errSuccess;
    err = JetOpenTable(
            _sessionId,
            _dbID,
            tableDefinition.name().c_str(),
            nullptr,
            0,
            0,
            &tableId);

    if (err != JET_errSuccess)
    {
        if (err == JET_errObjectNotFound)
        {
            tableInstance = createTable(tableDefinition);
        }
        else
        {
            throw jet_exception("Failed to open table_definition!", err);
        }
    }
    else
    {
        std::vector<std::shared_ptr<field_instance>> fieldInstances;
        // we need to get the column ids...
        const std::vector<std::shared_ptr<field_definition>>& fieldDefinitions = tableDefinition.tupleDefinition().fieldDefinitions();
        for (auto &fieldDefinition : fieldDefinitions)
        {
            JET_COLUMNDEF result;
            unsigned long resultSize = sizeof(JET_COLUMNDEF);

            jet_call(
                JetGetColumnInfo(
                    _sessionId,
                    _dbID,
                    tableDefinition.name().c_str(),
                    fieldDefinition->name().c_str(),
                    &result, 
                    resultSize,
                    JET_ColInfo));

            // We assume we only need the columnid since we know the rest.
            fieldInstances.push_back(createFieldInstance(fieldDefinition, result.columnid));
        }

        tableInstance = table_instance(
            tableDefinition,
            tableId,
            tuple_instance::create(fieldInstances));
    }

    return tableInstance;
}

table_instance db_jet_engine::createTable(
    const table_definition& tableDefinition
    )
{
    std::lock_guard<std::recursive_mutex> lock(_mutex);

    strings_buffer tempStrings;

    // Build an array of column definitions...
    std::vector<JET_COLUMNCREATE> eseColumns;
    const std::vector<std::shared_ptr<field_definition>>& fieldDefinitions = tableDefinition.tupleDefinition().fieldDefinitions();
    for (auto &fieldDefinition : fieldDefinitions)
    {
        JET_COLUMNCREATE eseColumnDefinition;
        memset(&eseColumnDefinition, 0, sizeof(JET_COLUMNCREATE));
        eseColumnDefinition.cbStruct = sizeof(JET_COLUMNCREATE);

        std::wstring* tempColumnName = new std::wstring(fieldDefinition->name());      // We need to create it on the heap to make sure the buffer 
        tempStrings.strings.push_back(tempColumnName);               // does not move until JetCreateTableColumnIndex() is complete.
        eseColumnDefinition.szColumnName = const_cast<wchar_t*>(tempColumnName->c_str());

        switch (fieldDefinition->type())
        {
        case type_int16:
            eseColumnDefinition.coltyp = JET_coltypShort;
            eseColumnDefinition.cbMax = sizeof(unsigned short);
            break;
        case type_int32:
            eseColumnDefinition.coltyp = JET_coltypLong;
            eseColumnDefinition.cbMax = sizeof(unsigned int);
            break;
        case type_int64:
            eseColumnDefinition.coltyp = JET_coltypLongLong;
            eseColumnDefinition.cbMax = sizeof(unsigned int);
            break;
        case type_float32:
            eseColumnDefinition.coltyp = JET_coltypIEEESingle;
            eseColumnDefinition.cbMax = sizeof(float);
            break;
        //case type_string:
        //    _ASSERTE(fieldDefinition->maxBufferSizeInBytes() <= 255);
        //    eseColumnDefinition.coltyp = JET_coltypText;
        //    eseColumnDefinition.cbMax = fieldDefinition->maxBufferSizeInBytes();
        //    eseColumnDefinition.cp = fieldDefinition->codePage();
        //    break;
        case type_string_long: // greater than 255 ascii, or 127 unicode
            eseColumnDefinition.coltyp = JET_coltypLongText;
            eseColumnDefinition.cbMax = fieldDefinition->maxBufferSizeInBytes();
            eseColumnDefinition.cp = fieldDefinition->codePage();
            break;
        case type_binaryBlob:
            eseColumnDefinition.coltyp = JET_coltypLongBinary;
            eseColumnDefinition.cbMax = fieldDefinition->maxBufferSizeInBytes();
            eseColumnDefinition.grbit = JET_bitColumnTagged;
            break;
        default:
            _ASSERTE(false);
        }

        // column options
        if (fieldDefinition->fixed())
        {
            eseColumnDefinition.grbit |= JET_bitColumnFixed;
        }
        if (fieldDefinition->autoIncrement())
        {
            eseColumnDefinition.grbit |= JET_bitColumnAutoincrement;
        }
        eseColumnDefinition.err = JET_errSuccess;

        eseColumns.push_back(eseColumnDefinition);
    }

    // Build an array of column definitions...
    std::vector<JET_INDEXCREATE3> eseIndices;
    std::vector<index_definition> indices = tableDefinition.indices();
    for (auto &index : indices)
    {
        JET_INDEXCREATE3 eseIndexDefinition;
        memset(&eseIndexDefinition, 0, sizeof(JET_INDEXCREATE3));
        eseIndexDefinition.cbStruct = sizeof(JET_INDEXCREATE3);
        std::wstring* tempIndexName = new std::wstring(index.name());      // We need to create it on the heap to make sure the buffer 
        tempStrings.strings.push_back(tempIndexName);                          // does not move until JetCreateTableColumnIndex() is complete.
        eseIndexDefinition.szIndexName = const_cast<wchar_t*>(tempIndexName->c_str());
        if (index.primary())
        {
            eseIndexDefinition.grbit |= JET_bitIndexPrimary;
        }
        if (index.unique())
        {
            eseIndexDefinition.grbit |= JET_bitIndexUnique;
        }

        // Calculate the buffer...
        std::wstring* tempKey = new std::wstring();
        const std::vector<index_field_definition>& indexFields = index.indexFields();
        for (auto &indexField : indexFields)
        {
            std::shared_ptr<field_definition> fieldDefinition = tableDefinition.tupleDefinition().field(indexField.userFieldId());

            (*tempKey) += indexField.ascending() ? L"+" : L"-";
            (*tempKey) += std::wstring(fieldDefinition->name().c_str(), fieldDefinition->name().size() + 1); // +/- and \0
        }
        tempStrings.strings.push_back(tempKey);
        eseIndexDefinition.szKey = const_cast<wchar_t*>(tempKey->c_str());
        eseIndexDefinition.cbKey = ( static_cast<unsigned long>(tempKey->size()) + 1 ) * sizeof(wchar_t);
        eseIndexDefinition.err = JET_errSuccess;

        eseIndices.push_back(eseIndexDefinition);
    }

    JET_TABLECREATE4 eseTableDefinition;
    memset(&eseTableDefinition, 0, sizeof(JET_TABLECREATE4));
    eseTableDefinition.cbStruct = sizeof(JET_TABLECREATE4);
    std::wstring* tempTableName = new std::wstring(tableDefinition.name());
    tempStrings.strings.push_back(tempTableName);
    eseTableDefinition.szTableName = const_cast<wchar_t*>(tempTableName->c_str());
    eseTableDefinition.rgcolumncreate = eseColumns.size() ? &(eseColumns[0]) : nullptr;
    eseTableDefinition.cColumns = static_cast<unsigned long>(eseColumns.size());
    eseTableDefinition.rgindexcreate = eseIndices.size() ? &(eseIndices[0]) : nullptr;
    eseTableDefinition.cIndexes = static_cast<unsigned long>(eseIndices.size());
    eseTableDefinition.grbit = 0;

    JET_ERR err = JET_errSuccess;
    err = JetCreateTableColumnIndex4(
        _sessionId, 
        _dbID,
        &eseTableDefinition);

    if (JET_errSuccess != err)
    {
        if (err != JET_errTableDuplicate)
        {
            // We could read the .err attribute of each column/index if we need to know which one exactly failed.
            throw jet_exception("Failed to create table_definition!", err);
        }
    }

    // Read and store the new ids...

    std::vector<std::shared_ptr<field_instance>> fieldInstances;

    unsigned int i = 0;
    for (auto &fieldDefinition : fieldDefinitions)
    {
        fieldInstances.push_back(createFieldInstance(fieldDefinition, eseColumns[i++].columnid));
    }

    table_instance tableInstance(
        tableDefinition,
        eseTableDefinition.tableid,
        tuple_instance::create(fieldInstances));

    return tableInstance;
}

void db_jet_engine::insert(
    const table_instance& tableInstance,
    const tuple_values& tupleValues)
{
    std::lock_guard<std::recursive_mutex> lock(_mutex);

    writeRow(tableInstance, tupleValues, insertRecord);
}

void db_jet_engine::update(
    const table_instance& tableInstance,
    const select_criteria& selectCriteria,
    const tuple_values& newValues
    )
{
    std::lock_guard<std::recursive_mutex> lock(_mutex);

    forEachMatch(
        tableInstance,
        selectCriteria,
            [&]()
            {
                updateCurrentRow(tableInstance, newValues);
            }
        );
}

void db_jet_engine::read(
        const table_instance& tableInstance,
        const select_criteria& selectCriteria,
        const std::vector<field_id_b>& selectedFields,
        std::vector<tuple_values>& readRows
        )
{
    std::lock_guard<std::recursive_mutex> lock(_mutex);

    forEachMatch(
        tableInstance,   // which table
        selectCriteria,
        [&]()
        {
            readCurrentRow(
                tableInstance,
                selectedFields,
                readRows);
        }
    );
}

void db_jet_engine::dispose(
        const table_instance& tableInstance,
        const select_criteria& selectCriteria
        )
{
    std::lock_guard<std::recursive_mutex> lock(_mutex);

    forEachMatch(
        tableInstance,   // which table
        selectCriteria,
        [&]()
        {
            deleteCurrentRow(tableInstance);
        }
    );
}

void db_jet_engine::readAll(
        const table_instance& tableInstance,
        const object_id& indexId,
        const std::vector<field_id_b>& selectedFields,
        std::vector<tuple_values>& readRows
        )
{
    std::lock_guard<std::recursive_mutex> lock(_mutex);

    forAll(
        tableInstance,
        indexId,
        [&]()
        {
            readCurrentRow(
                tableInstance,
                selectedFields,
                readRows);
        }
    );
}

void db_jet_engine::disposeAll(
    const table_instance& tableInstance,
    const object_id& indexId
    )
{
    std::lock_guard<std::recursive_mutex> lock(_mutex);

    forAll(
        tableInstance,
        indexId,
        [&]()
        {
            deleteCurrentRow(tableInstance);
        }
    );
}

void db_jet_engine::writeRow(
        const table_instance& tableInstance,
        const tuple_values& tupleValues,
        const operation_type& operationType
        )
{
    _ASSERTE( (operationType == insertRecord) || (operationType == updateRecord));

    const std::shared_ptr<const std::vector<std::shared_ptr<field_value>>> newValues = tupleValues.getValues();

    jet_call(
        ::JetBeginTransaction3(
            _sessionId,
            0,
            0));

    try
    {
        jet_call(
            ::JetPrepareUpdate(
                _sessionId,
                tableInstance.id(),
                operationType == insertRecord ? JET_prepInsert : JET_prepReplace
                ));

        // Build the columns array...
        std::vector<JET_SETCOLUMN> eseValues;
        for (auto &newValue : *newValues)
        {
            if (newValue->set() == false)
            {
                continue;
            }

            JET_SETCOLUMN eseValue = { 0 }; 
            JET_SETCOLUMN eseSizeValue = { 0 };

            std::shared_ptr<field_instance> fieldInstance = tableInstance.tupleInstance()->field(newValue->fieldInstanceId());

            eseValue.columnid = fieldInstance->fieldInstanceId(); 
            eseValue.pvData = newValue->buffer(); 
            eseValue.cbData = static_cast<unsigned long>(newValue->size());

            if ( fieldInstance->fixed() == false )
            {
                eseValue.grbit = JET_bitSetSeparateLV;

                if ( newValue->appendOnUpdate() == true )
                {
                    eseValue.grbit = JET_bitSetAppendLV; 
                }
                else
                {
                    eseValue.grbit = JET_bitSetOverwriteLV; 

                    unsigned int blobSize = 0;
                    blobSize = static_cast<unsigned int>(newValue->size());;

                    eseSizeValue.columnid = fieldInstance->fieldInstanceId();
                    eseSizeValue.pvData = &blobSize;
                    eseSizeValue.cbData = blobSize;
                    eseSizeValue.grbit = JET_bitSetSizeLV;
                    eseSizeValue.itagSequence = 1;
                    eseSizeValue.err = JET_errSuccess;

                    eseValues.push_back(eseSizeValue);
                }
                // tagged columns == multi-valued columents.
                // itagSequence = 0 : append a new value.
                // itagSequence = n (where n > 0), set value #n.
                eseValue.itagSequence = 1;
            }
            eseValue.err = JET_errSuccess; 

            eseValues.push_back(eseValue);
        }

        jet_call(
            ::JetSetColumns(
                _sessionId,
                tableInstance.id(),
                &(eseValues[0]), static_cast<unsigned long>(eseValues.size())
                )); 

        jet_call(
            ::JetUpdate2(
                _sessionId,
                tableInstance.id(),
                0,
                0,
                0,
                0));

        jet_call(
            ::JetCommitTransaction(
                _sessionId,
                0)); 
    }
    catch(jet_exception& e)
    {
        ::JetRollback(_sessionId, 0); 
        throw e;
    }
}

void db_jet_engine::updateCurrentRow(
    const table_instance& tableInstance,
    const tuple_values& tupleValues)
{
    writeRow(tableInstance, tupleValues, updateRecord);
}

void db_jet_engine::readCurrentRow(
    const table_instance& tableInstance,
    const std::vector<field_id_b>& selectedFields,
    std::vector<tuple_values>& readRows
    )
{
    JET_ERR err = JET_errSuccess;

    std::shared_ptr<std::vector<std::shared_ptr<field_value>>> fieldValues =
        std::make_shared<std::vector<std::shared_ptr<field_value>>>();

    for (auto &userFieldId : selectedFields)
    {
        unsigned long readBytes = 0;

        std::shared_ptr<field_instance> fieldInstance = tableInstance.tupleInstance()->fieldByUserFieldId(userFieldId.id);

        switch (fieldInstance->type())
        {
        case type_int16:
            {
                unsigned short value;
                jet_call(
                    ::JetRetrieveColumn(_sessionId,
                        tableInstance.id(),
                        fieldInstance->fieldInstanceId(),
                        &value,
                        sizeof(value),
                        &readBytes,
                        0,
                        0
                        ),
                        "Failed to read type_int16 value from table_definition! JetRetrieveColumn()");

                _ASSERTE(readBytes == sizeof(value));

                fieldValues->push_back(createValue<unsigned short>(fieldInstance->fieldInstanceId(), value));
            }
            break;
        case type_int32:
            {
                int value;
                jet_call(
                    ::JetRetrieveColumn(
                        _sessionId,
                        tableInstance.id(),
                        fieldInstance->fieldInstanceId(),
                        &value,
                        sizeof(value),
                        &readBytes,
                        0,
                        0),
                        "Failed to read type_int32 value from table_definition! JetRetrieveColumn()");

                _ASSERTE(readBytes == sizeof(value));

                fieldValues->push_back(createValue<unsigned int>(fieldInstance->fieldInstanceId(), value));
            }
            break;
        case type_int64:
            {
                __int64 value;
                jet_call(
                    ::JetRetrieveColumn(_sessionId,
                        tableInstance.id(),
                        fieldInstance->fieldInstanceId(),
                        &value,
                        sizeof(value),
                        &readBytes,
                        0,
                        0),
                        "Failed to read type_int64 value from table_definition! JetRetrieveColumn()");

                _ASSERTE(readBytes == sizeof(value));

                fieldValues->push_back(createValue<unsigned __int64>(fieldInstance->fieldInstanceId(), value));
            }
            break;

        case type_float32:
            {
                float value;
                jet_call(
                    ::JetRetrieveColumn(_sessionId,
                        tableInstance.id(),
                        fieldInstance->fieldInstanceId(),
                        &value,
                        sizeof(value),
                        &readBytes,
                        0,
                        0),
                        "Failed to read type_float32 value from table_definition! JetRetrieveColumn()");

                _ASSERTE(readBytes == sizeof(value));

                fieldValues->push_back(createValue<float>(fieldInstance->fieldInstanceId(), value));
            }
            break;
        //case type_string:
        case type_string_long:
            {
                unsigned int sizeInBytes = fieldInstance->maxBufferSizeInBytes();

                string_buffer stringBuffer(sizeInBytes + sizeof(wchar_t));

                jet_call(
                    ::JetRetrieveColumn(_sessionId,
                        tableInstance.id(),
                        fieldInstance->fieldInstanceId(),
                        stringBuffer.buffer,
                        sizeInBytes,
                        &readBytes,
                        0,
                        0
                        ),
                        "Failed to read type_string/type_string_long value from table_definition! JetRetrieveColumn()");

                //if (fieldDefinition->type() == type_string)
                //{
                //    // type_string is a fixed length type, so strings will always be appended with spaces.
                //    _ASSERTE(readBytes == sizeInBytes);

                //    stringBuffer.rightTrimSpaces();
                //}
                //else
                //{
                    stringBuffer.buffer[readBytes / sizeof(wchar_t)] = L'\0';
                //}

                fieldValues->push_back(createValue<std::wstring>(fieldInstance->fieldInstanceId(), std::wstring(stringBuffer.buffer)));
            }
            break;
        case type_binaryBlob:
            {
                JET_ERR returnValue = 
                    ::JetRetrieveColumn(
                        _sessionId,                 // session id
                        tableInstance.id(),   // table id
                        fieldInstance->fieldInstanceId(),   // column id
                        nullptr,                    // buffer
                        0,                          // buffer size
                        &readBytes,                 // actual size of column value
                        0,
                        0
                    );

                std::shared_ptr<field_value_t<binary_blob>> value;

                if (returnValue != JET_wrnBufferTruncated)
                {
                    jet_call(returnValue);
                }

                if (readBytes != 0)
                {
                    binary_blob blob(readBytes);

                    jet_call(
                        ::JetRetrieveColumn(
                        _sessionId,                       // session id
                        tableInstance.id(),               // table id
                        fieldInstance->fieldInstanceId(), // column id
                        static_cast<void*>(blob._buffer), // buffer
                        blob._size,                       // buffer size
                        &readBytes,                       // actual size of column value
                        0,
                        0
                        ),
                        "Failed to read type_blob value from table_definition! JetRetrieveColumn()");

                    _ASSERTE(readBytes == blob._size);

                    fieldValues->push_back(createValue<binary_blob>(fieldInstance->fieldInstanceId(), blob));
                }
                else
                {
                    fieldValues->push_back(createValue<binary_blob>(fieldInstance->fieldInstanceId(), binary_blob()));
                }

                fieldValues->push_back(value);
            }
            break;
        default:
            _ASSERTE(false);
        }
    }// for each column...

    readRows.push_back(tuple_values(tableInstance.tupleInstance(), fieldValues));
}

void db_jet_engine::deleteCurrentRow(
    const table_instance& tableInstance
    )
{
    jet_call(
        ::JetDelete(
            _sessionId,
            tableInstance.id()
            ));
}

void db_jet_engine::makeKey(
    const table_instance& tableInstance,
    const select_criteria& selectCriteria,
    const key_type& keyType)
{
    // how many fields are set?
    unsigned int setFieldCount = 0;
    std::shared_ptr<const std::vector<std::shared_ptr<field_value>>> values = selectCriteria.values().getValues();
    for (auto &value : *values)
    {
        if (value->set() == false)
        {
            break;
        }
        ++setFieldCount;
    }

    // iterate and build the key...
    unsigned int valueIndex = 0;
    for (auto &value : *values)
    {
        if (valueIndex == setFieldCount)
        {
            // this means it was a partial key, exit now...
            break;
        }
        JET_GRBIT flags = 0;

        // if first field...
        if (valueIndex == 0)
        {
            flags |= JET_bitNewKey;
        }

        // if last field...
        if (valueIndex == (setFieldCount - 1))
        {
            switch (keyType)
            {
            case key_type::start_key: flags |= JET_bitFullColumnStartLimit;
                break;
            case key_type::end_key: flags |= JET_bitFullColumnEndLimit;
                break;
            }
        }

        field_type type = tableInstance.tupleInstance()->field(value->fieldInstanceId())->type();
        switch (type)
        {
        case type_int16:
            {
                std::shared_ptr<field_value_t<unsigned short>> valueT = std::dynamic_pointer_cast<field_value_t<unsigned short>>(value);
                unsigned short id = valueT->value();
                jet_call(
                    ::JetMakeKey(
                        _sessionId,
                        tableInstance.id(),
                        &id,
                        sizeof(id),
                        flags
                        ));
            }
            break;
        case type_int32:
            {
                std::shared_ptr<field_value_t<unsigned int>> valueT = std::dynamic_pointer_cast<field_value_t<unsigned int>>(value);
                unsigned int id = valueT->value();
                jet_call(
                    ::JetMakeKey(
                        _sessionId,
                        tableInstance.id(),
                        &id, sizeof(id),
                        flags
                        ));
            }
            break;
        case type_int64:
            {
                std::shared_ptr<field_value_t<unsigned __int64>> valueT = std::dynamic_pointer_cast<field_value_t<unsigned __int64>>(value);
                unsigned __int64 id = valueT->value();
                jet_call(
                    ::JetMakeKey(
                        _sessionId,
                        tableInstance.id(),
                        &id,
                        sizeof(id),
                        flags
                        ));
            }
            break;
        case type_float32:
            {
                std::shared_ptr<field_value_t<float>> valueT = std::dynamic_pointer_cast<field_value_t<float>>(value);
                float id = valueT->value();
                jet_call(
                    ::JetMakeKey(
                        _sessionId,
                        tableInstance.id(),
                        &id,
                        sizeof(id),
                        flags
                        ));
            }
            break;
        //case type_string:
        case type_string_long:
            {
                std::shared_ptr<field_value_t<std::wstring>> valueT = std::dynamic_pointer_cast<field_value_t<std::wstring>>(value);
                std::wstring id = valueT->value();
                jet_call(
                    ::JetMakeKey(
                        _sessionId,
                        tableInstance.id(),
                        id.c_str(),
                        static_cast<unsigned long>(id.size() * sizeof(wchar_t)),
                        flags
                        ));
            }
            break;
        default:
            throw std::logic_error("type cannot be used as a key!");
        }
        ++valueIndex;
    }
}

bool db_jet_engine::setStartKey(
    const table_instance& tableInstance,
    const select_criteria& selectCriteria
    )
{
    JET_ERR err = JET_errSuccess;

    makeKey(tableInstance, selectCriteria, key_type::start_key);

    switch (selectCriteria.comparisonOperator())
    {
    case equal_operator:
        err = ::JetSeek(_sessionId, tableInstance.id(), JET_bitSeekEQ );
        break;
    case equal_or_great_than_operator:
        err = ::JetSeek(_sessionId, tableInstance.id(), JET_bitSeekGE );
        break;
    }

    return err >= 0;
}

bool db_jet_engine::setEndKey(
    const table_instance& tableInstance,
    const select_criteria& selectCriteria)
{
    JET_ERR err = JET_errSuccess;

    makeKey(tableInstance, selectCriteria, key_type::end_key);

    err = ::JetSetIndexRange(_sessionId, tableInstance.id(), JET_bitRangeInclusive | JET_bitRangeUpperLimit);
    return err >= 0;
}

void db_jet_engine::forEachMatch(
    const table_instance& tableInstance,
    const select_criteria& selectCriteria,
    for_each_func func
    )
{
    jet_call(
        ::JetSetCurrentIndex4(
            _sessionId,
            tableInstance.id(),
            selectCriteria.index()->name().c_str(),
            0,  // pindexid
            0,  // grbit
            1   // itagSequence
            ));

    if (false == setStartKey(tableInstance, selectCriteria))
    {
        return;
    }

    if (false == setEndKey(tableInstance, selectCriteria))
    {
        return;
    }

    // Loop from start key to end key...
    JET_ERR err = JET_errSuccess;
    do
    {
        func();

        err = ::JetMove(_sessionId, tableInstance.id(), JET_MoveNext, 0);
    } while (!(err < 0));
}

void db_jet_engine::forAll(
    const table_instance& tableInstance,
    const object_id& indexId,
    for_each_func func)
{
    JET_ERR err = JET_errSuccess;

    jet_call(
        ::JetSetCurrentIndex4(
            _sessionId,
            tableInstance.id(),
            tableInstance.index(indexId)->name().c_str(),
            0,  // pindexid
            0,  // grbit
            1   // itagSequence
            ));

    for(err = ::JetMove(_sessionId, tableInstance.id(), JET_MoveFirst, 0);
        JET_errSuccess == err;
        err = ::JetMove(_sessionId, tableInstance.id(), JET_MoveNext, 0))
    {
        func();
    }
}

std::shared_ptr<db_jet_engine> test_wrappers::storage::createEngine()
{
    return db_jet_engine::create();
}
